#include <iostream>
#include <fstream>
#include <string>
using namespace std;

#include "../config.h"
#include "../Adler-32 checksum/Adler32.h"

#include "deflate.h"

// Deflate - theoretical maximal size for BLOCK_SIZE
// =================================================
// These are the worst cases I can think of:
// -----------------------------------------
// - all triplets are (symbol,0,0) => 1b of expansion per triplet
// - all triplets are (symbol,3,32768)
//   --> symbol=9b,length=9b,distance=5+13=18b;all=36b;
//       --> the triplet stands for 4 bytes => 4*8=32b; ==> 36-32=4b of expansion per triplet
// ==> of the cases it is obvious that maximal expandion is 4bits/triplet; 4b=0.5B;
//     + each block is ended by END_OF_BLOCK symbol which takes 7b; 7b ~ 1B
//     ==> MAX_EXPANSION = ceil(0.5*BLOCK_SIZE)+1; ==> BUFFER_SIZE = ceil(1.5*BLOCK_SIZE)+1

// TODO: checksum has to be calculated on original blocks of data!
int main(int argc, char **argv)
{
    if(argc != 3)
    {
        cout << "Usage: ./prc_seq file_to_compress.any       compressed_file.any.prc\n"
             << " or    ./prc_seq file_to_decompress.any.prc decompressed_file.any" << endl;
        return 1;
    }
    ifstream fin(argv[1], ios_base::in | ios_base::binary);
    if(!fin.is_open()) { cerr << "File reading error!" << endl; return 1; }
    ofstream fout(argv[2], ios_base::out | ios_base::binary);
    if(!fout.is_open()) { cerr << "File writing error!" << endl; return 1; }
    // compression or decompression??
    bool decompress = false;
    if(strcmp(argv[1] + strlen(argv[1]) - 4, ".prc") == 0)
        decompress = true;
    //
    streamsize read;
    unsigned int enc_len, dec_len, bit_pos, checksum;
	const unsigned int BUFFER_SIZE = int(1.5*double(Config::BLOCK_SIZE)) + 1 + 1;	// 2nd +1 is there instead of ceil() function
    unsigned char *encoded = new unsigned char[BUFFER_SIZE];		// encoded buffer - be aware of expanssion --> BUFFER_SIZE
    unsigned char *decoded = new unsigned char[Config::BLOCK_SIZE];	// decoded buffer - it can be never larger than BLOCK_SIZE
    //
    // DECOMPRESSION
    if(decompress)
    {
        // check file type
        fin.read((char *)encoded, 3);
        if(encoded[0] != 'P' || encoded[1] != 'R' || encoded[2] != 'C')
        {
            cerr << "Invalid format!" << endl;
            return 1;
        }
        // decompression
		int block_i = 0;
        while(1)
        {
            fin.read((char *)&enc_len, sizeof(unsigned int));	// read a length of a compressed block
            fin.read((char *)encoded, enc_len);					// read a compressed block of length `enc_len`
			fin.read((char *)&checksum, sizeof(unsigned int));	// read the checksum for this block
            if((read = fin.gcount()) > 0)       // get the count of read bytes in the last read operation
            {
				block_i++;
                if(!Adler32::Check(encoded, enc_len, checksum))  // check the checksum
                {
                    cerr << "File is corrupted. Invalid checksum!" << endl;
                    return 2;
                }
                // decompress a single block
				enc_len *= 8;	// convert bytes to bits (upper boundary, because the last byte doesn't need to be complete)
				memset(decoded, 0, Config::BLOCK_SIZE);
				dec_len = 0;
				bit_pos = 0;
				unsigned short lit, len, dist;
				while(bit_pos <= enc_len)
				{
					Huffman_Decode_Symbol(encoded, bit_pos, lit); // literal/length
					if(lit < 256)   // literal
						decoded[dec_len++] = (unsigned char)lit;
					else if(lit == 256) // end of block
						break;
					else //if(lit > 256)    // length
					{
						Length_Decode(lit, encoded, bit_pos, len);    // length
						Distance_Decode(encoded, bit_pos, dist); // distance
						for(int p = dec_len-dist; len > 0; --len, ++p)	// `p` must never be negative!!! (if it is, there is some error)
							decoded[dec_len++] = decoded[p];
					}
					//else { WTF??!! This shouldn't happen ;-) }
				}
                // write out the decompressed data
                fout.write((char *)decoded, dec_len);
            }
            if(fin.eof()) break;
        }
    }
    //
    // COMPRESSION
    else
    {
        fout.write("PRC", 3);
        while(1)
        {
            fin.read((char *)decoded, Config::BLOCK_SIZE);
            if((read = fin.gcount()) > 0)
            {
                // compress a single block
				bit_pos = 0;
				memset(encoded, 0, BUFFER_SIZE);
				//
				vector<Triplet> triplets;
				LZ77_Encode((const unsigned char *)decoded, read, triplets, 4096, 258, 3);
				for(size_t i = 0, im = triplets.size(); i < im; i++)
				{
					if(triplets[i].i > 0) Length_Encode(triplets[i].j, encoded, bit_pos);   // encode length
					if(triplets[i].j > 0) Distance_Encode(triplets[i].i, encoded, bit_pos); // distance
					Huffman_Encode_Symbol(triplets[i].a, bit_pos, encoded); // literal
				}
				Insert_EndOfBlock_Symbol(bit_pos, encoded);	// EOB symbol(==256)
				//
				enc_len = (bit_pos/8) + int((bit_pos%8)>0);	// number of bytes
                checksum = Adler32::Compute(encoded, enc_len);
                // write out
                fout.write((char *)&enc_len, sizeof(unsigned int));     // encoded block length (for faster decompression - especially in parallel)
                fout.write((char *)encoded, enc_len);					// encoded block
                fout.write((char *)&checksum, sizeof(unsigned int));	// checksum of the encoded block
            }
            if(fin.eof()) break;
        }
    }
    fout.close();
    fin.close();
    //
    delete [] encoded;
    delete [] decoded;
    //
    return 0;
}